﻿using System;
using System.ComponentModel;
using System.Data;
using System.Text;
using System.Threading;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Data;
using System.Globalization;
using ResourceBlender.DAL;
using System.Collections.Generic;
using System.Linq;
using ResourceBlender.Core;

namespace ResourceBlender.Express {
    /// <summary>
    /// A form allowing a user to search for and select a resource
    /// </summary>
    public partial class WinFindResource : Window {
        private BackgroundWorker worker;
        private ObservableSortedDictionary<string, CultureInfo> languages;
        private string applicationId = string.Empty;

        private DataTemplate ascSorted,
                             descSorted;

        public static readonly DependencyProperty AllowNewResourceProperty = DependencyProperty.Register("AllowNewResource", typeof(bool), typeof(WinFindResource), new PropertyMetadata(false));
        /// <summary>
        /// Gets or sets whether an existing resource must be selected before the 'Accept' button can be pressed.
        /// </summary>
        /// <value><c>true</c> if no search results allow creation of a new resource and accept the dialog with no selected resource, otherwise <c>false</c>.</value>
        public bool AllowNewResource {
            get { return (bool)base.GetValue(AllowNewResourceProperty); }
            set { base.SetValue(AllowNewResourceProperty, value); }
        }

        /// <summary>
        /// Gets the ID of the selected resource.
        /// </summary>
        /// <value>The ID of the selected resource.</value>
        public int SelectedResource {
            get {
                int resourceId = -1;
                int.TryParse((lstResources.SelectedValue ?? "-1").ToString(), out resourceId);
                return resourceId;
            }
        }

        /// <summary>
        /// Gets or sets the language of the search text.
        /// </summary>
        /// <value>The language of the search text.</value>
        public string SearchLanguage {
            get { return (cboLanguages.SelectedValue ?? string.Empty).ToString(); }
            set {
                if(languages.ContainsKey(value)) {
                    cboLanguages.SelectedValue = value;
                }
            }
        }

        /// <summary>
        /// Gets or sets the search text/phrase.
        /// </summary>
        /// <value>The search text.</value>
        public string SearchText {
            get { return txtTranslation.Text; }
            set { txtTranslation.Text = value; }
        }

        /// <summary>
        /// Initializes a new <see cref="WinFindResource"/>.
        /// </summary>
        public WinFindResource() {
            InitializeComponent();
            ascSorted = (DataTemplate)FindResource("HeaderTemplateAsc");
            descSorted = (DataTemplate)FindResource("HeaderTemplateDesc");
            chkOnlyExisting.Visibility = Visibility.Collapsed;
            txtTranslation.Focus();
            LoadLanguages(string.Empty);
        }

        /// <summary>
        /// Initializes a new <see cref="WinFindResource"/> and searches for resources similar to the specified translation.
        /// </summary>
        /// <param name="translation">The translation.</param>
        /// <param name="applicationId">The application ID to use when filtering for existing resources.</param>
        public WinFindResource(RBTranslation translation, string applicationId) {
            InitializeComponent();
            ascSorted = (DataTemplate)FindResource("HeaderTemplateAsc");
            descSorted = (DataTemplate)FindResource("HeaderTemplateDesc");
            DataContext = translation;
            if(applicationId.Length > 0) {
                this.applicationId = applicationId;
            } else {
                chkOnlyExisting.Visibility = Visibility.Collapsed;
                btnSelect.Visibility = Visibility.Collapsed;
            }
            InitializeComponent();
            txtTranslation.Focus();
            LoadLanguages(translation.LanguageCode);
        }

        /// <summary>
        /// Initializes a new <see cref="WinFindResource"/> to search for translations in a specified language.
        /// </summary>
        /// <param name="application">The application whose default language to pre-select.</param>
        public WinFindResource(ObservableApplication application) {
            InitializeComponent();
            ascSorted = (DataTemplate)FindResource("HeaderTemplateAsc");
            descSorted = (DataTemplate)FindResource("HeaderTemplateDesc");
            InitializeComponent();
            txtTranslation.Focus();
            this.applicationId = application.ID;
            LoadLanguages(application.DefaultLanguage);
        }

        /// <summary>
        /// Populates the Language ComboBox.
        /// </summary>
        /// <param name="selectedLanguage">The language to pre-select.</param>
        private void LoadLanguages(string selectedLanguage) {
            SetStatus(true, "Loading available languages...");
            if(worker != null && worker.IsBusy) {
                while(worker.IsBusy) {
                    Thread.Sleep(500);
                }
            }
            worker = new BackgroundWorker();
            worker.DoWork += (s, dwe) => {
                try {
                    dwe.Result = ResourceBlenderDAL.GetUniqueLanguages();
                } catch(Exception ex) {
                    dwe.Result = ex.Message;
                }
            };
            worker.RunWorkerCompleted += (s, rwe) => {
                SetStatus(false, null);
                if(rwe.Error != null) {
                    DialogBox.Show(this, rwe.Error.Message, "Error", DialogBox.DialogBoxType.OK, DialogBox.DialogBoxIcon.Error);
                } else {
                    var langs = rwe.Result as IEnumerable<KeyValuePair<string, CultureInfo>>;
                    ObservableSortedDictionary<string, CultureInfo> uniqueLangs = new ObservableSortedDictionary<string, CultureInfo>(new Comparers.CultureInfoEnglshNameComparer(), new Comparers.StringEqualityComparer());
                    foreach(var lang in langs) {
                        uniqueLangs.Add(lang.Key, lang.Value);
                    }
                    if(langs != null) {
                        if(uniqueLangs.Count > 0) {
                            bool haveLanguageToSelect = false;
                            if(uniqueLangs.ContainsKey(selectedLanguage)) {
                                haveLanguageToSelect = true;
                            }
                            if(!haveLanguageToSelect && selectedLanguage.Length > 0) {
                                uniqueLangs.Add(selectedLanguage, Utils.GetCultureInfo(selectedLanguage));
                            }
                            this.languages = uniqueLangs;
                            cboLanguages.ItemsSource = languages;
                            if(haveLanguageToSelect) {
                                cboLanguages.SelectedValue = selectedLanguage;
                            }
                            ApplicationCommands.Find.Execute(null, txtTranslation);
                        } else {
                            DialogBox.Show(this, "No resources have been added.\n\nBefore you can search for resources, at least one must be added", "No Resources", DialogBox.DialogBoxType.OK, DialogBox.DialogBoxIcon.Warning);
                            Close();
                        }
                    }
                }
            };
            worker.RunWorkerAsync();
        }

        /// <summary>
        /// Sets the text on the status message.
        /// </summary>
        /// <param name="loading"><c>true</c> if the progress bar should be displayed, otherwise false.</param>
        /// <param name="message">The message.</param>
        private void SetStatus(bool loading, object message) {
            if(message == null) {
                lblStatus.Visibility = Visibility.Hidden;
            } else {
                lblStatus.Visibility = Visibility.Visible;
                lblStatus.Content = message;
            }
            prgStatus.Visibility = loading ? Visibility.Visible : Visibility.Hidden;
        }

        /// <summary>
        /// Clears the ListView when the selected language changes.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="System.Windows.Controls.SelectionChangedEventArgs"/> instance containing the event data.</param>
        private void cboLanguages_SelectionChanged(object sender, SelectionChangedEventArgs e) {
            if(lstResources != null) {
                lstResources.DataContext = null;
            }
        }

        /// <summary>
        /// Fires the Search command if 'Live Filters' is checked
        /// </summary>
        /// <param name="sender">The sender.</param>
        /// <param name="e">The <see cref="System.Windows.Controls.SelectionChangedEventArgs"/> instance containing the event data.</param>
        private void UpdateFilters(object sender, EventArgs e) {
            if(!this.IsLoaded || (chkLiveFilter.IsChecked != true)) {
                return;
            }
            ApplicationCommands.Find.Execute(null, null);
        }

        /// <summary>
        /// Searches for resources.
        /// </summary>
        /// <param name="sender">The sender.</param>
        /// <param name="e">The <see cref="System.Windows.Input.ExecutedRoutedEventArgs"/> instance containing the event data.</param>
        private void FindResources(object sender, ExecutedRoutedEventArgs e) {
            if(cboLanguages.SelectedIndex < 0 || string.IsNullOrEmpty(txtTranslation.Text)) {
                return;
            }
            string languageCode = cboLanguages.SelectedValue.ToString(),
                   searchText;
            bool onlyExisting = chkOnlyExisting.IsChecked == true;
            switch(cboCriteria.SelectedIndex) {
                case 1:
                    searchText = txtTranslation.Text + '%';
                    break;
                case 2:
                    searchText = '%' + txtTranslation.Text;
                    break;
                case 0:
                default:
                    searchText = '%' + txtTranslation.Text + '%';
                    break;
            }
            SetStatus(true, "Searching...");
            if(worker != null && worker.IsBusy) {
                while(worker.IsBusy) {
                    Thread.Sleep(500);
                }
            }
            worker = new BackgroundWorker();
            worker.DoWork += (s, dwe) => {
                DataTable dtResults = ResourceBlenderDAL.FindResources(languageCode, searchText, onlyExisting ? applicationId : null);
                dwe.Result = dtResults;
            };
            worker.RunWorkerCompleted += (s, rwe) => {
                if(rwe.Error != null) {
                    DialogBox.ShowAlert(this, "Error: Could not connect to database - " + rwe.Error.Message, "Error");
                } else if(rwe.Result is DataTable) {
                    DataTable dtResults = rwe.Result as DataTable;
                    lstResources.DataContext = dtResults;
                    int numResources = dtResults.Rows.Count;
                    SetStatus(false, numResources == 0 ? "No matches" : string.Format("{0} {1}", numResources, numResources == 1 ? "resource" : "resources"));
                }
            };
            worker.RunWorkerAsync();
        }

        /// <summary>
        /// Determines whether the Find Resources command can be executed.
        /// </summary>
        /// <param name="sender">The sender.</param>
        /// <param name="e">The <see cref="System.Windows.Input.CanExecuteRoutedEventArgs"/> instance containing the event data.</param>
        private void CanExecuteFindResources(object sender, CanExecuteRoutedEventArgs e) {
            e.CanExecute = !string.IsNullOrEmpty(txtTranslation.Text) && cboLanguages.SelectedIndex >= 0 && worker != null && !worker.IsBusy;
        }

        private void lst_SelectionChanged(object sender, SelectionChangedEventArgs e) {
            if(AllowNewResource) {
                btnSelect.Content = lstResources.SelectedValue == null ? "Create Resource" : "Select Resource";
            }
        }

        /// <summary>
        /// Selects a resource and closes the dialog on double click.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="System.Windows.RoutedEventArgs"/> instance containing the event data.</param>
        private void lst_MouseDoubleClick(object sender, RoutedEventArgs e) {
            ApplicationCommands.Save.Execute(null, this);
        }

        /// <summary>
        /// Determines whether the Select Resource command can be executed.
        /// </summary>
        /// <param name="sender">The sender.</param>
        /// <param name="e">The <see cref="System.Windows.Input.CanExecuteRoutedEventArgs"/> instance containing the event data.</param>
        private void CanExecuteSelectResource(object sender, CanExecuteRoutedEventArgs e) {
            e.CanExecute = lstResources.SelectedItem != null || // a resource is selected
                (AllowNewResource && !string.IsNullOrEmpty(txtTranslation.Text)); // either creation is allowed and translation text has been entered
        }

        /// <summary>
        /// Selects a resource and returns a DialogResult of true.
        /// </summary>
        /// <param name="sender">The sender.</param>
        /// <param name="e">The <see cref="System.Windows.Input.ExecutedRoutedEventArgs"/> instance containing the event data.</param>
        private void SelectResource(object sender, ExecutedRoutedEventArgs e) {
            DialogResult = true;
        }

        /// <summary>
        /// Sorts the grid.
        /// </summary>
        /// <param name="sender">The sender.</param>
        /// <param name="e">The <see cref="System.Windows.RoutedEventArgs"/> instance containing the event data.</param>
        private void OnColumnHeaderClick(object sender, RoutedEventArgs e) {
            if(e.OriginalSource is GridViewColumnHeader) { // sort the grid
                GridViewColumn column = ((GridViewColumnHeader)e.OriginalSource).Column;
                string sortPath = column.DisplayMemberBinding == null ? (column.CellTemplate.DataType ?? string.Empty).ToString() : (column.DisplayMemberBinding as Binding).Path.Path;
                if(sortPath.Length == 0) {
                    return; // no field specified
                }
                if(column.HeaderTemplate == ascSorted) {
                    column.HeaderTemplate = null;
                    (lstResources.DataContext as DataTable).DefaultView.Sort = sortPath + " DESC"; ;
                } else if(column.HeaderTemplate == descSorted) {
                    column.HeaderTemplate = ascSorted;
                    (lstResources.DataContext as DataTable).DefaultView.Sort = "";
                } else {
                    column.HeaderTemplate = ascSorted;
                    (lstResources.DataContext as DataTable).DefaultView.Sort = sortPath + " ASC";
                }
            }
        }

        /// <summary>
        /// Shows the help file.
        /// </summary>
        /// <param name="sender">The sender.</param>
        /// <param name="e">The <see cref="System.Windows.Input.ExecutedRoutedEventArgs"/> instance containing the event data.</param>
        private void ShowHelp(object sender, ExecutedRoutedEventArgs e) {
            WPFUtils.OpenHelp("Translating.an.Application.html#reusing");
        }

        /// <summary>
        /// Closes the window.
        /// </summary>
        /// <param name="sender">The sender.</param>
        /// <param name="e">The <see cref="System.Windows.Input.ExecutedRoutedEventArgs"/> instance containing the event data.</param>
        private void CloseWindow(object sender, ExecutedRoutedEventArgs e) {
            Close();
        }
    }
}
